package com.chris.core.socket.nio.util;

import com.chris.core.socket.bio.dto.SocketMapDto;
import com.chris.core.socket.nio.dto.SocketChannelMapDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketOption;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Nio socket 服务端
 * 苏斌 2022-3-26 自己的轮子
 */
public class SockerNioServerUtil {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * 线程池
     * 1. newCachedThreadPool 创建一个可缓存线程池，如果线程池长度超过处理需要，可灵活回收空闲线程，若无可回收，则新建线程。
     * 2. newFixedThreadPool 创建一个定长线程池，可控制线程最大并发数，超出的线程会在队列中等待。
     * 3. newScheduledThreadPool 创建一个定长线程池，支持定时及周期性任务执行。
     * 4. newSingleThreadExecutor 创建一个单线程化的线程池，它只会用唯一的工作线程来执行任务，保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
     */
    private ExecutorService executorService =  Executors.newCachedThreadPool();

    /**
     * 端口
     */
    private int port;

    /**
     * 最大socket线程数
     */
    private int socketCount;

    /**
     * 消息断言
     */
    private String breakMark = "\r\n";// \n    \r\n    ~!@#bbq8$%^&*\n;

    /**
     * 客服端在线数
     */
    private int onlineCount = 0;
    /**
     * selector数量
     */
    private int selectorCount=5;
    /**
     * 服务端通道
     */
    ServerSocketChannel serverChannel;

    /**
     * Socket服务端
     */
    //ServerSocket serverSocket;

    /**
     * selector-hash-集合
     */
    private ConcurrentHashMap<String, Selector> selectorMap = new ConcurrentHashMap<>();
    /**
     * selector-hash-集合
     */
    private ConcurrentHashMap<String, SocketChannelMapDTO> SocketChannelMap = new ConcurrentHashMap<>();

    public  SockerNioServerUtil(int port) throws IOException {
        IntSystem(port,25000,"\r\n");
    }

    private void IntSystem(int port,int socketCount, String breakMark) throws IOException {
        this.port = port;
        this.socketCount=socketCount;
        this.breakMark=breakMark;

        //--------初始化 Selector 群组-------
        Selector selector = Selector.open();
        //建立一个总 接入 ACCEPT selector
        selectorMap.put("ACCEPT",selector);
        //多线程 READ/WRITE selector
        for (int i = 0; i < selectorCount; i++) {
            selector = Selector.open();
            selectorMap.put("READ_"+i,selector);
            selector = Selector.open();
            selectorMap.put("WRITE_"+i,selector);
           // serverChannel.register(selector, SelectionKey.OP_ACCEPT);
           // selectorMap.put("ACCEPT",selector);
        }
        //--------------------------------


        //-----启动OPEN 监听--
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(6000);
                    Open();
                } catch (InterruptedException | IOException e) {
                    e.printStackTrace();
                }


            }
        });
        t.start();

    }


    /**
     * 打开端口
     * @throws IOException
     */
    public void Open() throws IOException, InterruptedException {
        serverChannel = ServerSocketChannel.open();//new ServerSocket();//
        //------基础参数设置----------
        //-------设置非阻塞-----------
        serverChannel.configureBlocking(false);
        //serverSocket.setReceiveBufferSize(serverSocket.getReceiveBufferSize() * 2);
        ServerSocket serverSocket = serverChannel.socket();
        InetSocketAddress address = new InetSocketAddress(port);
        serverSocket.bind(address, 100);
        //-------------------------
        //注册accept事件
        Selector selector_ACCEPT =selectorMap.get("ACCEPT");
        serverChannel.register(selector_ACCEPT,SelectionKey.OP_ACCEPT);
        //--------------------------


        //-----启动监听ACCEPT--
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    ListenAccept();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        t.start();

        //-----启动监听 read--
        t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ListenRead();

            }
        });
        t.start();

        //-----监听  wirte--
        t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ListenWrite();

            }
        });
        t.start();

        log.info("启动NIO Socket 服务监听 端口:"+port);
    }


    //#region Listen监听

    /**
     * 监听ACCEPT
     */
    public void ListenAccept() throws InterruptedException, IOException {
        int ls=0;
        Selector selector_ACCEPT =selectorMap.get("ACCEPT");
        while (true) {
            //限制:每秒100个链接
            Thread.sleep(10);


            //大于最大连接数不接受新的连接
            if (socketCount <= getOnlineCount())// || true
            {
                Thread.sleep(1000);
                continue;
            }

            //循环使用不同selector
            ls++;
            if (ls >= selectorCount)
                ls = 0;

            // Selector selector = selectorMap.get(ls);
            //------------注册关注事件--------------
            int icount = 0;
            try {
                icount = selector_ACCEPT.select(1000);
                if (icount == 0)
                    continue;
            } catch (Exception ex) {
                //accept 超时，重新来
                log.debug("selector超时" + ex.toString());
                continue;
            }
            if (icount > 0) {
                //--------通过seleltor获取通道-----------
                // selector.keys() //代表所有注册到 selector上的通道
                // selector.selectedKeys()//注册到这个selector有事件发生
                Set<SelectionKey> selectionkeys = selector_ACCEPT.selectedKeys();
                Iterator<SelectionKey> iterator = selectionkeys.iterator();
                //--------------同步方式执行Accept-----------------
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    //轮询问：Accept， 其实相当于只论序 ServerSocketChannel 的 Accept ， socketChannel放过
                    if (key.isAcceptable()) {
                        //如果是客服端接入事件。给客服端生成channel
                        SocketChannel socketChannel = serverChannel.accept();
                        //设置socketChannel为非阻塞模式
                        socketChannel.configureBlocking(false);
                        String uuid = UUID.randomUUID().toString();
                        SocketChannelMapDTO dto = new SocketChannelMapDTO(uuid,socketChannel);
                        //---------------------
                        //将socketChannel注册到selecttor，注册关注事件，并给定beffer
                        Selector selector_read = selectorMap.get("READ_" + ls);
                        Selector selector_write = selectorMap.get("WRITE_" + ls);
                        //uuid 为附加参数 ，任何object
                        socketChannel.register(selector_read, SelectionKey.OP_READ,uuid);//, ByteBuffer.allocate(1024))
                        socketChannel.register(selector_write, SelectionKey.OP_WRITE,uuid);//, ByteBuffer.allocate(1024
                        addOnlineCount();
                        log.debug("建立客服端Socket链接 id:" + key.hashCode()+ "-> 总客服端数：" + getOnlineCount());
                    }


                    //移除当前SelectionKey，防止重复操作
                    iterator.remove();
                }
            }
        }
    }

    /**
     * 监听 read
     */
    public void ListenRead()
    {
        int ls=0;
        while (true) {
            ls++;
            if (ls >= selectorCount)
                ls = 0;
            //循环检测不同的selector监听事件
            Selector selector_read = selectorMap.get("READ_" + ls);
            int icount = 0;
            try {
                icount = selector_read.select(1000);
                if (icount == 0)
                    continue;
            } catch (Exception ex) {
                //accept 超时，重新来
                log.debug("selector超时" + ex.toString());
                continue;
            }
            if (icount > 0) {
                //-----有事件执行下一步--------
                Set<SelectionKey> selectionkeys = selector_read.selectedKeys();
                Iterator<SelectionKey> iterator = selectionkeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    //轮询：写入事件
                    if (key.isWritable()) {
                        //获取关联子SocketChanne
                       // SocketChannel channel = (SocketChannel) key.channel();
                      //  String  uid = (String) key.attachment();
                        //写入动作
                        executorService.execute(new Runnable() {
                            @Override
                            public void run() {
                                DoReviceMsg(key);
                            }
                        });
                    }

                    //移除当前SelectionKey，防止重复操作
                    iterator.remove();
                }
            }
        }
    }

    /**
     * 监听  wirte
     */
    public void ListenWrite()
    {
        int ls=0;
        while (true) {
            ls++;
            if (ls >= selectorCount)
                ls = 0;
            //循环检测不同的selector监听事件
            Selector selector_write = selectorMap.get("WRITE_" + ls);
            int icount = 0;
            try {
                icount = selector_write.select(1000);
                if (icount == 0)
                    continue;
            } catch (Exception ex) {
                //accept 超时，重新来
                log.debug("selector超时" + ex.toString());
                continue;
            }
            if (icount > 0) {
                //-----有事件执行下一步--------
                Set<SelectionKey> selectionkeys = selector_write.selectedKeys();
                Iterator<SelectionKey> iterator = selectionkeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    //轮询问：Accept， 其实相当于只论序 ServerSocketChannel 的 Accept ， socketChannel放过

                    //轮询：写入事件
                    if (key.isWritable()) {
                        //获取关联子SocketChanne
                        //SocketChannel channel = (SocketChannel) key.channel();
                        //获取Beffur
                        String uid = (String) key.attachment();
                        //写入动作
                    }

                    //移除当前SelectionKey，防止重复操作
                    iterator.remove();
                }
            }
        }
    }

    //#endregion

    //#region 发送消息及其命令
    public void SendMsg(String msg,String uid) throws IOException {
        boolean isdone=false;
        for (int i = 0; i <selectorCount; i++) {
            for (SelectionKey key : selectorMap.get("READ_" + i).keys()
            ) {
                String keyid= (String)key.attachment();
                if (keyid.equals("uid")) {
                    Channel channel = key.channel();
                    if (channel instanceof  SocketChannel ) {
                        SocketChannel dest = (SocketChannel) channel;
                        SendMsg(msg, dest);
                        isdone = true;
                        break;
                    }
                }
            }
            if (isdone)
                break;
        }
    }

    public void SendMsg(String msg,SocketChannel target) throws IOException {
        synchronized(target) {
            ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8));
            target.write(buffer);
        }
    }
    /**
     * 发送文件
     */
    public void SendFile() throws IOException {
        //String fileName, File file
        //if (file==null || !file.isFile() || !file.exists())
        //    return;
        File file = new File("./tip_rev.txt");
        //文件流
        FileInputStream fileInputStream = new FileInputStream(file);
        //文件通道
        FileChannel fileChannel =fileInputStream.getChannel();
        //------------------创建bytefuffer----------------
        ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());
        //------------------------------------------------
        //将文件写入buffer
        fileChannel.read(byteBuffer);
        fileChannel.close();

    }

    //#endregion

    //#region 接收内容

    /**
     * 循环接收消息(文本）
     */
    private void DoReviceMsg(SelectionKey key) {
        String  uid = (String) key.attachment();
        SocketChannel socketChannel= (SocketChannel) key.channel();
        try {
            synchronized(socketChannel) {
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                while (socketChannel.read(byteBuffer) != -1) {
                    //不断读取
                    String msg = new String(byteBuffer.array());
                    byteBuffer.clear();
                }
            }

        } catch (Exception ex) {
            log.debug("服务端读->取内容断开,客户端离线" + ex.toString());


            try {
                //取消注册
                key.cancel();
                //关闭通道
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            // ex.printStackTrace();
        } finally {

         //   subOnlineCount();
          //  log.debug("服务端Socket链接关闭 id:" + dto.getCid() + "-> 总客服端数：" + getOnlineCount());
        }

    }

    /**
     *接收文件
     */
    public void ReviceFile() throws IOException {
        //--------------------------------------------
        File file = new File("./tip_rev.txt");
        if (file!=null && file.isFile() && file.exists() )
            file.delete();
        //-------------------------------------------


        String str="i am chris";
        //文件流
        FileOutputStream fileOutputStream  = new FileOutputStream(file);
        //文件通道
        FileChannel fileChannel =fileOutputStream.getChannel();


        //------------------创建bytefuffer----------------
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //写
        byteBuffer.put(str.getBytes(StandardCharsets.UTF_8));
        //反转
        byteBuffer.flip();

        //------------------写入filecnannel----------------
        fileChannel.write(byteBuffer);
        fileChannel.close();
        //-------------------------------------------------






    }


    //#endregion

    //#region 线程同步 及其他

    public synchronized int getOnlineCount() {
        return onlineCount;
    }

    public synchronized void addOnlineCount() {
        onlineCount++;
    }

    public synchronized void subOnlineCount() {
        onlineCount--;
    }

    /**
     * 流量控制  (目前策略 接收是 发送 2 被 限流 ，双方需要约定好 ）
     * @param passedlen 总字节
     * @param beginTime 开始时间
     */
    private void flowControl(long passedlen ,long beginTime)
    {
        //流量控制 200KB/S = 200  * 1024  b/s  = 204800 b/S =  205 b /ms
        long passTime = (System.currentTimeMillis() - beginTime);
        long pp = passedlen / 420 - passTime;
        if (pp > 0) {
            try {
                Thread.sleep(pp);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 去除字符串后缀
     * @param msg
     * @return
     */
    private String delSuffixMsg(String msg) {
        if (msg.endsWith(breakMark)) {
            msg= msg.substring(0,msg.length()-breakMark.length());
        }
        return msg;
    }
    //#endregion
}


